#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "BMC.h"
#include "../List.h"
#include "../Statistics.h"

extern Statistics* ExecutionStatistics;

//Operations for BMCBranchInstructionInfo
BMCBranchInstructionInfo* BMCBIFNew(uint16_t TargetAddress, uint32_t Info){
    BMCBranchInstructionInfo* New;
    
    New = malloc(sizeof(BMCBranchInstructionInfo));
    New->BranchFlags = Info;
    New->Target = TargetAddress;
    
    return New;
}

//Operations for BMCInstruction
BMCInstruction* BMCINew(uint16_t Address, uint8_t Opcode, uint8_t Size, BMCInstructionType InstructionType, uint8_t Operand1, uint8_t Operand2){
    BMCInstruction* New;
    
    New = malloc(sizeof(BMCInstruction));
    New->Addr = Address;
    New->Opcode = Opcode;
    New->Size = Size;
    New->InstructionType = InstructionType;
    New->Operands[0] = Operand1;
    New->Operands[1] = Operand2;
    return New;
}

/*Operations for BMCEntryPoint*/
BMCEntryPoint*  BMCEPNew(){
    BMCEntryPoint* New;
    
    New = malloc(sizeof(BMCEntryPoint));  
    New->Data = 0;
    New->Children = malloc(sizeof(BMCEntryPoint*) * 16);
    memset(New->Children, 0, 16 * sizeof(BMCEntryPoint*));    
    
    return New;
}

static inline int hash(int chave){
    return (chave & 0xF);
}

static void BMCEPInsert_(BMCEntryPoint* Entry, uint16_t Addr, uint8_t Data, uint32_t Depth){
    if(Depth == 4){
        Entry->Data = Data;
        return;
    }
    else{
        int pos;
        pos = hash(Addr >> (Depth * 4));

        if(!Entry->Children[pos])
            Entry->Children[pos] = BMCEPNew();

        BMCEPInsert_(Entry->Children[pos], Addr, Data, Depth+1);
    }
}

void BMCEPInsert(BMCEntryPoint *EntryPoints, uint16_t Addr, uint8_t Data){
    BMCEPInsert_(EntryPoints, Addr, Data, 0);
}

static uint8_t BMCEPSearch_(BMCEntryPoint* Entry, uint16_t Addr, uint32_t Depth){
    if(Depth == 4){
        return Entry->Data;
    }
    else{
        int pos;
        pos = hash(Addr >> (Depth * 4));

        if(!Entry->Children[pos]){
            return 0;
        }
        return BMCEPSearch_(Entry->Children[pos], Addr, Depth+1);
    }
}

uint8_t BMCEPSearch(BMCEntryPoint *Entry, uint16_t Addr){
    return BMCEPSearch_(Entry, Addr, 0);
}

void BMCEPDump_(BMCEntryPoint *Entry, uint16_t Data, uint32_t Depth, uint32_t Flags , void (*fn)(uint16_t, void*), void* Args){
    uint32_t i;
    
    if(Depth == 4){
        if(Flags & 0x01){
            if(Entry->Data){
                printf("Addr: %04X\n", Data);
            }
        }
        else if(Flags & 0x02){
            if(Entry->Data){
                fn(Data, Args);
            }
        }
        return;
    }
    else{
        for(i = 0; i < 16; i++){
            if(Entry->Children[i]){
                BMCEPDump_(Entry->Children[i], Data | (i << (Depth * 4)), Depth + 1, Flags, fn, Args);
            }
        }
    }
}

void BMCEPDump(BMC *Block){
    BMCEPDump_(Block->EntryPoints, 0, 0, 1, NULL, NULL);
}

void BMCEPExecute(BMC *Block, void (*fn)(uint16_t, void*), void* Args){
    BMCEPDump_(Block->EntryPoints, 0, 0, 2, fn, Args);
}

//Operations for BMC
BMC* BMCNew(uint16_t StartingAddress){
    BMC* Block;
    
    ExecutionStatistics->DBT_TotalBMCs++;

    Block = malloc(sizeof(BMC));
    Block->Instructions = malloc(sizeof(BMCInstruction*) * BMC_INITIAL_INSTR);
    Block->Position = 0;
    Block->Length = 0;
    Block->InstructionBufferLength = BMC_INITIAL_INSTR;
    Block->StartAddr = StartingAddress;
    Block->EntryPoints = BMCEPNew();
    Block->JobFlags = 0;
    Block->IR = NULL;
    Block->CompleteECTXTUnoptimized = NULL;
    Block->PartialECTXTUnoptimized = NULL;
    Block->BlockSize = 0;
    Block->Pure = 0;
    Block->Cycles = 0;
    Block->ContextsOptimized = 0;
    return Block;
}

void BMCAddInstruction(BMC *Block, BMCInstruction *Instruction){
    Block->Instructions[Block->Position] = Instruction;
    Block->Position++;
    Block->Length++;
    if(Block->Position == Block->InstructionBufferLength){
        Block->InstructionBufferLength += BMC_STEP_INSTR;
        Block->Instructions = 
                realloc(Block->Instructions, Block->InstructionBufferLength);
    }
}

void BMCSetJobFlags(BMC* Block, uint32_t Flags){
    Block->JobFlags = Flags;
}

void BMCAddJobFlags(BMC* Block, uint32_t Flags){
    Block->JobFlags |= Flags;
}

void BMCAddEntryPoint(BMC* Block, uint16_t EntryAddress){
    BMCEPInsert(Block->EntryPoints, EntryAddress, 1);
}

uint32_t BMCGetJobFlags(BMC* Block){
    return Block->JobFlags;
}

uint8_t BMCSearchEntryPoint(BMC* Block, uint16_t EntryAddress){
    return BMCEPSearch(Block->EntryPoints, EntryAddress);
}

static int BlockListComparator(void* X, void* Y){
    if(X == Y)
        return 0;
    else
        return 1;
}

//Operations for BMCList
BMCList* BMCLNew(){
    BMCList* New;
    New = malloc(sizeof(BMCList));
    New->Length = BMCL_INITIAL_SIZE;
    New->Pos = 0;
    New->Blocks = malloc(sizeof(BMC*) * BMCL_INITIAL_SIZE);
    memset(New->Blocks, 0, (sizeof(BMC*) * BMCL_INITIAL_SIZE));
    
    New->AnalyzeBlocks = LST_New(BlockListComparator);
    
    return New;
}

void BMCLAdd(BMCList* List, BMC* Block){
    List->Blocks[List->Pos] = Block;
    List->Pos++;
    
    if(List->Pos == List->Length){
        List->Length += BMCL_STEP;
        List->Blocks = realloc(List->Blocks,  List->Length);
    }
}

uint32_t BMCLNumBlocks(BMCList* List){
    return List->Pos;
}

BMC* BMCLGetBlock(BMCList* List, uint32_t BlockId){
    return List->Blocks[BlockId];
}

void BMCLDestroy(BMCList* List){
    free(List->Blocks);
    free(List->AnalyzeBlocks);
}
